package org.jmc.refmanager.loader;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

public class CSVParser {
    private boolean trim = true;
    private BufferedReader reader = null;
    private String line;
    private int lineCount;
    private String[] columnNames;
    private char delimiter = ';';

    /**
     * Protected empty constructor to allow extension
     *
     * @throws IOException on error
     */
    protected CSVParser() throws IOException {
    }

    public CSVParser(String file) throws IOException {
        this.reader = new BufferedReader(new FileReader(file));
        readFirstLine();
    }

    public CSVParser(String file, String encoding) throws IOException {
        this.reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), encoding));
        readFirstLine();
    }

    public CSVParser(BufferedReader reader) throws IOException {
        this.reader = reader;
        readFirstLine();
    }

    /**
     * Extended constructor for CSV files without header
     *
     * @param file        csv file name
     * @param columnNames column names array
     * @throws IOException on error
     */
    public CSVParser(String file, String[] columnNames) throws IOException {
        this.reader = new BufferedReader(new FileReader(file));
        this.columnNames = columnNames;
        readNextLine();
    }

    /**
     * Extended constructor for CSV files without header
     * 
     * @param file
     * @param encoding
     * @param delimiter
     * @param columnNames
     * @throws IOException
     */
    public CSVParser(String file, String encoding, char delimiter, String[] columnNames) throws IOException {
        this.reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), encoding));
        this.delimiter = delimiter;
        if (columnNames == null) {
        	 readFirstLine();
        } else {
        	this.columnNames = columnNames;
            readNextLine();
        }
    }

    /**
     * Extended constructor for CSV files without header
     *
     * @param reader      file reader
     * @param columnNames column names array
     * @throws IOException on error
     */
    public CSVParser(BufferedReader reader, String[] columnNames) throws IOException {
        this.reader = reader;
        this.columnNames = columnNames;
        readNextLine();
    }

    /**
	 * Reads the next non-empty line. If EOF has been reached, {@link line} is
	 * left <code>null</code>.
	 */
    protected void readNextLine() throws IOException {
        do {
        	line = reader.readLine();
        	lineCount++;
        	if (line == null) {
        		return; // end of file reached
        	}
        	
            if (trim) {
                line = line.trim();
            }
        } while (line.length() == 0);
    }

    protected void readFirstLine() throws IOException {
        readNextLine();
        SimpleStringTokenizer tokenizer = new SimpleStringTokenizer(line, delimiter);
        List<String> columnList = new ArrayList<String>();
        while (tokenizer.hasMoreTokens()) {
            String value = tokenizer.nextToken();
            if (trim) {
                value = value.trim();
            }
            columnList.add(value);
        }
        columnNames = columnList.toArray(new String[columnList.size()]);
        readNextLine();
    }

    public boolean hasNext() throws IOException {
        return line != null;
    }

    public Map<String, String> next() throws IOException {
        Map<String, String> attributes = new HashMap<String, String>();

        SimpleStringTokenizer tokenizer = new SimpleStringTokenizer(line, delimiter);
        for (int i = 0; i < columnNames.length; i++) {
            // allow optional columns
            if (tokenizer.hasMoreTokens()) {
                // skip unmapped fields
                String fieldName = columnNames[i];
                String value = tokenizer.nextToken();
                if (trim) {
                    value = value.trim();
                }
                if (fieldName.length() > 0) {
                    attributes.put(fieldName, value);
                }
            }
        }
        readNextLine();
        return attributes;
    }

    public void close() throws IOException {
        reader.close();
    }

    public void setTrim(boolean trim) {
        this.trim = trim;
    }

    /**
     * Tokenizer for CSV Strings (Comma separated values).<br>
     * Actually it should be called semi-colon separated values
     */
    public class SimpleStringTokenizer {

        /**
         * token delimiter
         */
        private char delimiter = ';';
        /**
         * String to parse
         */
        private String csvString;
        /**
         * Current index
         */
        private int currentIndex = 0;

        /**
         * Constructor.
         *
         * @param aString String to parse
         */
        public SimpleStringTokenizer(String aString) {
            csvString = aString;
        }

        /**
         * Constructor.
         *
         * @param aString    String to parse
         * @param aDelimiter Token Separator
         */
        public SimpleStringTokenizer(String aString, char aDelimiter) {
            csvString = aString;
            delimiter = aDelimiter;
        }

        /**
         * test if there is remaining tokens.
         *
         * @return true on remaining tokens
         */
        public boolean hasMoreTokens() {
            return (currentIndex >= 0);
        }

        /**
         * Get the next token.
         *
         * @return next token
         * @throws NoSuchElementException no more tokens
         */
        public String nextToken() throws NoSuchElementException {
            int currentIndex = csvString.indexOf(delimiter, this.currentIndex);
            String currentToken;

            if (currentIndex < 0) {
                currentToken = csvString.substring(this.currentIndex);
                this.currentIndex = -1;

            } else {
                currentToken = csvString.substring(this.currentIndex, currentIndex);
                this.currentIndex = currentIndex + 1;
            }

            return currentToken;
        }
    }

	public String[] getHeader() {
		return columnNames;
	}
}
